#include "trigger_system.h"
#include "../parser/sql_parser.h"
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

// Utility function to get current time in microseconds
static uint64_t get_time_microseconds(void) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec;
}

// Hash function for table names
uint32_t trigger_table_hash(const char* table_name) {
    if (!table_name) return 0;
    
    uint32_t hash = 0;
    const char* p = table_name;
    while (*p) {
        hash = hash * 31 + *p;
        p++;
    }
    return hash % 256;
}

// Convert trigger event to string
const char* trigger_event_to_string(trigger_event_t event) {
    switch (event) {
        case TRIGGER_EVENT_INSERT: return "INSERT";
        case TRIGGER_EVENT_UPDATE: return "UPDATE";
        case TRIGGER_EVENT_DELETE: return "DELETE";
        case TRIGGER_EVENT_ALL: return "ALL";
        default: return "UNKNOWN";
    }
}

// Convert trigger timing to string
const char* trigger_timing_to_string(trigger_timing_t timing) {
    switch (timing) {
        case TRIGGER_TIMING_BEFORE: return "BEFORE";
        case TRIGGER_TIMING_AFTER: return "AFTER";
        case TRIGGER_TIMING_INSTEAD_OF: return "INSTEAD OF";
        default: return "UNKNOWN";
    }
}

// Convert trigger status to string
const char* trigger_status_to_string(trigger_status_t status) {
    switch (status) {
        case TRIGGER_STATUS_ACTIVE: return "ACTIVE";
        case TRIGGER_STATUS_INACTIVE: return "INACTIVE";
        case TRIGGER_STATUS_DISABLED: return "DISABLED";
        case TRIGGER_STATUS_ERROR: return "ERROR";
        default: return "UNKNOWN";
    }
}

// Create trigger system
trigger_system_t* trigger_system_create(const trigger_system_config_t* config, 
                                        gudb* database) {
    if (!config || !database) {
        return NULL;
    }
    
    trigger_system_t* system = calloc(1, sizeof(trigger_system_t));
    if (!system) {
        return NULL;
    }
    
    // Copy configuration
    memcpy(&system->config, config, sizeof(trigger_system_config_t));
    system->database = database;
    
    // Allocate trigger array
    system->triggers = calloc(config->max_triggers, sizeof(trigger_definition_t*));
    if (!system->triggers) {
        free(system);
        return NULL;
    }
    
    // Initialize synchronization primitives
    if (pthread_rwlock_init(&system->triggers_lock, NULL) != 0 ||
        pthread_mutex_init(&system->stats_mutex, NULL) != 0) {
        free(system->triggers);
        free(system);
        return NULL;
    }
    
    system->start_time = time(NULL);
    system->next_trigger_id = 1;
    system->initialized = false;
    
    return system;
}

// Initialize trigger system
int trigger_system_init(trigger_system_t* system, logger_t* logger) {
    if (!system || system->initialized) {
        return -1;
    }
    
    system->logger = logger;
    system->initialized = true;
    
    LOG_INFO("TRIGGER_SYSTEM", 
             "Trigger system initialized: max_triggers=%u, max_sql_length=%u",
             system->config.max_triggers,
             system->config.max_sql_length);
    
    return 0;
}

// Destroy trigger system
void trigger_system_destroy(trigger_system_t* system) {
    if (!system) {
        return;
    }
    
    LOG_INFO("TRIGGER_SYSTEM", "Shutting down trigger system");
    
    // Clean up all triggers
    pthread_rwlock_wrlock(&system->triggers_lock);
    
    for (uint32_t i = 0; i < system->config.max_triggers; i++) {
        trigger_definition_t* trigger = system->triggers[i];
        if (trigger) {
            // Clean up trigger action data
            if (trigger->action_type == TRIGGER_ACTION_SQL && trigger->action.sql.sql_statements) {
                free(trigger->action.sql.sql_statements);
            }
            
            if (trigger->condition) {
                free(trigger->condition);
            }
            
            pthread_mutex_destroy(&trigger->mutex);
            free(trigger);
        }
    }
    
    pthread_rwlock_unlock(&system->triggers_lock);
    
    // Clean up synchronization primitives
    pthread_rwlock_destroy(&system->triggers_lock);
    pthread_mutex_destroy(&system->stats_mutex);
    
    // Free memory
    free(system->triggers);
    free(system);
    
    LOG_INFO("TRIGGER_SYSTEM", "Trigger system destroyed");
}

// Create a new trigger
int trigger_system_create_trigger(trigger_system_t* system,
                                 const char* name,
                                 const char* table_name,
                                 trigger_event_t events,
                                 trigger_timing_t timing,
                                 trigger_action_type_t action_type,
                                 const char* action_data,
                                 const char* condition) {
    if (!system || !name || !table_name || !action_data) {
        return -1;
    }
    
    pthread_rwlock_wrlock(&system->triggers_lock);
    
    // Check if trigger already exists
    for (uint32_t i = 0; i < system->config.max_triggers; i++) {
        trigger_definition_t* existing = system->triggers[i];
        if (existing && strcmp(existing->name, name) == 0) {
            pthread_rwlock_unlock(&system->triggers_lock);
            return -1; // Trigger already exists
        }
    }
    
    // Find free slot
    uint32_t slot = 0;
    bool found_slot = false;
    for (uint32_t i = 0; i < system->config.max_triggers; i++) {
        if (!system->triggers[i]) {
            slot = i;
            found_slot = true;
            break;
        }
    }
    
    if (!found_slot) {
        pthread_rwlock_unlock(&system->triggers_lock);
        return -1; // No free slots
    }
    
    // Create trigger definition
    trigger_definition_t* trigger = calloc(1, sizeof(trigger_definition_t));
    if (!trigger) {
        pthread_rwlock_unlock(&system->triggers_lock);
        return -1;
    }
    
    // Initialize trigger
    trigger->trigger_id = system->next_trigger_id++;
    strncpy(trigger->name, name, sizeof(trigger->name) - 1);
    strncpy(trigger->table_name, table_name, sizeof(trigger->table_name) - 1);
    trigger->events = events;
    trigger->timing = timing;
    trigger->status = TRIGGER_STATUS_ACTIVE;
    trigger->action_type = action_type;
    trigger->created_time = time(NULL);
    trigger->modified_time = trigger->created_time;
    
    // Initialize mutex
    if (pthread_mutex_init(&trigger->mutex, NULL) != 0) {
        free(trigger);
        pthread_rwlock_unlock(&system->triggers_lock);
        return -1;
    }
    
    // Set up action data
    if (action_type == TRIGGER_ACTION_SQL) {
        trigger->action.sql.sql_statements = strdup(action_data);
        trigger->action.sql.statement_count = 1; // Simplified
        trigger->action.sql.use_transaction = true;
    } else if (action_type == TRIGGER_ACTION_FUNCTION) {
        // For function triggers, action_data should contain function pointer
        // This is a simplified implementation
        trigger->action.function.function = NULL; // Would be set appropriately
        trigger->action.function.user_data = NULL;
    }
    
    // Set condition if provided
    if (condition) {
        trigger->condition = strdup(condition);
        trigger->has_condition = true;
    }
    
    // Add to system
    system->triggers[slot] = trigger;
    system->trigger_count++;
    
    // Add to appropriate event lists for faster execution
    uint32_t hash = trigger_table_hash(table_name);
    if (!system->table_triggers[hash]) {
        system->table_triggers[hash] = calloc(1, sizeof(trigger_definition_t*));
    }
    
    // Add to event-specific lists
    if (timing == TRIGGER_TIMING_BEFORE) {
        if (events & TRIGGER_EVENT_INSERT) {
            trigger->next = system->before_insert_triggers;
            system->before_insert_triggers = trigger;
        }
        if (events & TRIGGER_EVENT_UPDATE) {
            trigger->next = system->before_update_triggers;
            system->before_update_triggers = trigger;
        }
        if (events & TRIGGER_EVENT_DELETE) {
            trigger->next = system->before_delete_triggers;
            system->before_delete_triggers = trigger;
        }
    } else if (timing == TRIGGER_TIMING_AFTER) {
        if (events & TRIGGER_EVENT_INSERT) {
            trigger->next = system->after_insert_triggers;
            system->after_insert_triggers = trigger;
        }
        if (events & TRIGGER_EVENT_UPDATE) {
            trigger->next = system->after_update_triggers;
            system->after_update_triggers = trigger;
        }
        if (events & TRIGGER_EVENT_DELETE) {
            trigger->next = system->after_delete_triggers;
            system->after_delete_triggers = trigger;
        }
    }
    
    pthread_rwlock_unlock(&system->triggers_lock);
    
    LOG_INFO("TRIGGER_SYSTEM", 
             "Created trigger '%s' on table '%s' for %s %s events",
             name, table_name,
             trigger_timing_to_string(timing),
             trigger_event_to_string(events));
    
    return 0;
}

// Drop a trigger
int trigger_system_drop_trigger(trigger_system_t* system, const char* name) {
    if (!system || !name) {
        return -1;
    }
    
    pthread_rwlock_wrlock(&system->triggers_lock);
    
    // Find and remove trigger
    for (uint32_t i = 0; i < system->config.max_triggers; i++) {
        trigger_definition_t* trigger = system->triggers[i];
        if (trigger && strcmp(trigger->name, name) == 0) {
            // Remove from event lists (simplified removal)
            // In a full implementation, we would properly unlink from lists
            
            // Clean up trigger data
            if (trigger->action_type == TRIGGER_ACTION_SQL && trigger->action.sql.sql_statements) {
                free(trigger->action.sql.sql_statements);
            }
            
            if (trigger->condition) {
                free(trigger->condition);
            }
            
            pthread_mutex_destroy(&trigger->mutex);
            free(trigger);
            
            system->triggers[i] = NULL;
            system->trigger_count--;
            
            pthread_rwlock_unlock(&system->triggers_lock);
            
            LOG_INFO("TRIGGER_SYSTEM", "Dropped trigger '%s'", name);
            return 0;
        }
    }
    
    pthread_rwlock_unlock(&system->triggers_lock);
    return -1; // Trigger not found
}

// Find trigger by name
trigger_definition_t* trigger_system_find_trigger(trigger_system_t* system, 
                                                  const char* name) {
    if (!system || !name) {
        return NULL;
    }
    
    pthread_rwlock_rdlock(&system->triggers_lock);
    
    for (uint32_t i = 0; i < system->config.max_triggers; i++) {
        trigger_definition_t* trigger = system->triggers[i];
        if (trigger && strcmp(trigger->name, name) == 0) {
            pthread_rwlock_unlock(&system->triggers_lock);
            return trigger;
        }
    }
    
    pthread_rwlock_unlock(&system->triggers_lock);
    return NULL;
}

// Execute triggers for a specific event
int trigger_system_execute_triggers(trigger_system_t* system,
                                   const char* table_name,
                                   trigger_event_t event,
                                   trigger_timing_t timing,
                                   gudb_txn* transaction,
                                   trigger_row_data_t* old_row,
                                   trigger_row_data_t* new_row) {
    if (!system || !table_name) {
        return -1;
    }
    
    // Check nesting level
    if (system->current_nesting_level >= system->config.max_nesting_level) {
        LOG_WARN("TRIGGER_SYSTEM", "Maximum nesting level reached, skipping triggers");
        return -1;
    }
    
    pthread_rwlock_rdlock(&system->triggers_lock);
    
    // Get appropriate trigger list based on timing and event
    trigger_definition_t* trigger_list = NULL;
    if (timing == TRIGGER_TIMING_BEFORE) {
        if (event == TRIGGER_EVENT_INSERT) {
            trigger_list = system->before_insert_triggers;
        } else if (event == TRIGGER_EVENT_UPDATE) {
            trigger_list = system->before_update_triggers;
        } else if (event == TRIGGER_EVENT_DELETE) {
            trigger_list = system->before_delete_triggers;
        }
    } else if (timing == TRIGGER_TIMING_AFTER) {
        if (event == TRIGGER_EVENT_INSERT) {
            trigger_list = system->after_insert_triggers;
        } else if (event == TRIGGER_EVENT_UPDATE) {
            trigger_list = system->after_update_triggers;
        } else if (event == TRIGGER_EVENT_DELETE) {
            trigger_list = system->after_delete_triggers;
        }
    }
    
    int executed_count = 0;
    int error_count = 0;
    
    // Execute triggers for this table
    trigger_definition_t* current = trigger_list;
    while (current) {
        if (current->status == TRIGGER_STATUS_ACTIVE &&
            strcmp(current->table_name, table_name) == 0 &&
            (current->events & event)) {
            
            // Create execution context
            trigger_context_t* context = trigger_context_create(
                system->database, transaction, table_name, event, timing);
            if (!context) {
                error_count++;
                current = current->next;
                continue;
            }
            
            context->old_row = old_row;
            context->new_row = new_row;
            context->start_time_us = get_time_microseconds();
            
            // Execute trigger
            system->current_nesting_level++;
            system->in_trigger_execution = true;
            
            int result = 0;
            if (current->action_type == TRIGGER_ACTION_SQL) {
                // Execute SQL statements
                // This is a simplified implementation
                result = 0; // Would execute SQL here
            } else if (current->action_type == TRIGGER_ACTION_FUNCTION) {
                // Call function
                if (current->action.function.function) {
                    result = current->action.function.function(context);
                }
            }
            
            context->execution_time_us = get_time_microseconds() - context->start_time_us;
            
            system->current_nesting_level--;
            system->in_trigger_execution = (system->current_nesting_level > 0);
            
            // Update trigger statistics
            pthread_mutex_lock(&current->mutex);
            current->execution_count++;
            current->total_execution_time_us += context->execution_time_us;
            current->last_execution_time = time(NULL);
            
            if (result != 0 || context->has_error) {
                current->error_count++;
                error_count++;
                LOG_ERROR("TRIGGER_SYSTEM", 
                         "Trigger '%s' execution failed: %s",
                         current->name, 
                         context->error_message[0] ? context->error_message : "Unknown error");
            } else {
                executed_count++;
                if (system->config.log_trigger_execution) {
                    LOG_DEBUG("TRIGGER_SYSTEM", 
                             "Executed trigger '%s' on table '%s' in %llu microseconds",
                             current->name, table_name, context->execution_time_us);
                }
            }
            pthread_mutex_unlock(&current->mutex);
            
            // Update system statistics
            pthread_mutex_lock(&system->stats_mutex);
            system->stats.total_executions++;
            if (result == 0 && !context->has_error) {
                system->stats.successful_executions++;
            } else {
                system->stats.failed_executions++;
            }
            system->stats.total_execution_time_us += context->execution_time_us;
            if (context->execution_time_us > system->stats.max_execution_time_us) {
                system->stats.max_execution_time_us = context->execution_time_us;
            }
            pthread_mutex_unlock(&system->stats_mutex);
            
            trigger_context_destroy(context);
        }
        
        current = current->next;
    }
    
    pthread_rwlock_unlock(&system->triggers_lock);
    
    if (error_count > 0) {
        LOG_WARN("TRIGGER_SYSTEM", 
                 "Executed %d triggers with %d errors for %s %s on table '%s'",
                 executed_count + error_count, error_count,
                 trigger_timing_to_string(timing),
                 trigger_event_to_string(event),
                 table_name);
        return -1;
    }
    
    return executed_count;
}

// Create row data
trigger_row_data_t* trigger_row_data_create(uint32_t column_count) {
    trigger_row_data_t* row_data = calloc(1, sizeof(trigger_row_data_t));
    if (!row_data) {
        return NULL;
    }
    
    row_data->column_count = column_count;
    row_data->column_names = calloc(column_count, sizeof(char*));
    row_data->values = calloc(column_count, sizeof(char*));
    row_data->is_null = calloc(column_count, sizeof(bool));
    
    if (!row_data->column_names || !row_data->values || !row_data->is_null) {
        trigger_row_data_destroy(row_data);
        return NULL;
    }
    
    return row_data;
}

// Destroy row data
void trigger_row_data_destroy(trigger_row_data_t* row_data) {
    if (!row_data) {
        return;
    }
    
    if (row_data->column_names) {
        for (uint32_t i = 0; i < row_data->column_count; i++) {
            free(row_data->column_names[i]);
        }
        free(row_data->column_names);
    }
    
    if (row_data->values) {
        for (uint32_t i = 0; i < row_data->column_count; i++) {
            free(row_data->values[i]);
        }
        free(row_data->values);
    }
    
    free(row_data->is_null);
    free(row_data);
}

// Set column value in row data
int trigger_row_data_set_column(trigger_row_data_t* row_data, 
                               uint32_t column_index,
                               const char* column_name,
                               const char* value,
                               bool is_null) {
    if (!row_data || column_index >= row_data->column_count) {
        return -1;
    }
    
    // Set column name
    free(row_data->column_names[column_index]);
    row_data->column_names[column_index] = column_name ? strdup(column_name) : NULL;
    
    // Set value
    free(row_data->values[column_index]);
    row_data->values[column_index] = (value && !is_null) ? strdup(value) : NULL;
    row_data->is_null[column_index] = is_null;
    
    return 0;
}

// Get column value from row data
const char* trigger_row_data_get_column(trigger_row_data_t* row_data,
                                       const char* column_name) {
    if (!row_data || !column_name) {
        return NULL;
    }
    
    for (uint32_t i = 0; i < row_data->column_count; i++) {
        if (row_data->column_names[i] && 
            strcmp(row_data->column_names[i], column_name) == 0) {
            return row_data->is_null[i] ? NULL : row_data->values[i];
        }
    }
    
    return NULL;
}

// Create trigger context
trigger_context_t* trigger_context_create(gudb* database, 
                                         gudb_txn* transaction,
                                         const char* table_name,
                                         trigger_event_t event,
                                         trigger_timing_t timing) {
    if (!database || !table_name) {
        return NULL;
    }
    
    trigger_context_t* context = calloc(1, sizeof(trigger_context_t));
    if (!context) {
        return NULL;
    }
    
    context->database = database;
    context->transaction = transaction;
    context->table_name = table_name;
    context->event = event;
    context->timing = timing;
    context->should_continue = true;
    context->has_error = false;
    
    return context;
}

// Destroy trigger context
void trigger_context_destroy(trigger_context_t* context) {
    if (context) {
        free(context);
    }
}

// Get system statistics
int trigger_system_get_stats(trigger_system_t* system, 
                            trigger_system_stats_t* stats) {
    if (!system || !stats) {
        return -1;
    }
    
    pthread_mutex_lock(&system->stats_mutex);
    memcpy(stats, &system->stats, sizeof(trigger_system_stats_t));
    
    // Update calculated fields
    if (stats->total_executions > 0) {
        stats->avg_execution_time_us = 
            (double)stats->total_execution_time_us / stats->total_executions;
    }
    stats->total_triggers = system->trigger_count;
    stats->last_stats_update = time(NULL);
    
    pthread_mutex_unlock(&system->stats_mutex);
    
    return 0;
}

// Print system statistics
void trigger_system_print_stats(trigger_system_t* system) {
    if (!system) {
        return;
    }
    
    trigger_system_stats_t stats;
    if (trigger_system_get_stats(system, &stats) != 0) {
        return;
    }
    
    LOG_INFO("TRIGGER_STATS", 
             "Total Triggers: %llu, Active: %llu, Executions: %llu, "
             "Success: %llu, Failed: %llu, Avg Time: %.2f us, Max Time: %llu us",
             stats.total_triggers,
             stats.active_triggers,
             stats.total_executions,
             stats.successful_executions,
             stats.failed_executions,
             stats.avg_execution_time_us,
             stats.max_execution_time_us);
}

// Built-in trigger functions
int trigger_log_changes(trigger_context_t* context) {
    if (!context) return -1;
    
    LOG_INFO("TRIGGER_AUDIT", 
             "Change logged for table '%s': %s %s",
             context->table_name,
             trigger_timing_to_string(context->timing),
             trigger_event_to_string(context->event));
    
    return 0;
}

int trigger_audit_changes(trigger_context_t* context) {
    if (!context) return -1;
    
    // Simple audit implementation
    time_t now = time(NULL);
    LOG_INFO("TRIGGER_AUDIT", 
             "Audit: %s %s on table '%s' at %ld",
             trigger_timing_to_string(context->timing),
             trigger_event_to_string(context->event),
             context->table_name,
             now);
    
    return 0;
}

int trigger_validate_data(trigger_context_t* context) {
    if (!context) return -1;
    
    // Simple validation - just check if new_row exists for INSERT/UPDATE
    if ((context->event == TRIGGER_EVENT_INSERT || 
         context->event == TRIGGER_EVENT_UPDATE) && 
        !context->new_row) {
        strcpy(context->error_message, "Data validation failed: missing new row data");
        context->has_error = true;
        return -1;
    }
    
    return 0;
}

int trigger_update_timestamp(trigger_context_t* context) {
    if (!context || !context->new_row) return -1;
    
    // This would update a timestamp column in the new row
    // Simplified implementation just logs the action
    LOG_DEBUG("TRIGGER_TIMESTAMP", 
              "Updated timestamp for table '%s'", 
              context->table_name);
    
    return 0;
}